﻿using FFmpeg.AutoGen;
using FFmpegLib;
using MediaLib.Interface;
using Silk.NET.OpenAL;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace MediaLib.Implement
{
    /// <summary>
    /// 基于OpenAL的音频播放器
    /// </summary>
    public sealed unsafe class SilkAudioPlayer : IAudioPlayer
    {
        #region OpenAL obj
        SilkOpenALUtilities _openAL;
        private BufferFormat target_fmt;
        private AVSampleFormat out_sample_fmt;
        #endregion

        private CancellationTokenSource _cancelSource;

        public SourceState CurCtlStatus { get; set; }
        public string AudioFile { get; private set; }
        public int Size { get; private set; }
        public long Bitrate { get; private set; }
        public int ChannelNum { get; private set; }
        public int DepthBit { get; private set; }
        public int PerSampleSize { get; private set; }
        public int SampleRate { get; private set; }
        public FAVStream AudioStreamInfo { get; private set; }

        private FAudioFifo Fifo;
        private FSwrContext SwrCtx;

        public Action<FAVStream> DisplayInfo;

        public SilkAudioPlayer(string audioFile)
        {
            AudioFile = audioFile;
            if (string.IsNullOrEmpty(AudioFile)) { throw new ArgumentException("未指定音频文件"); }
            CurCtlStatus = SourceState.Initial;
        }

        public void Play()
        {
            InFmtCtx ??= new FAVFormatContext();
            if (!InFmtCtx.FmtCtxIsOpen)
                InFmtCtx.OpenFmtCtx(AudioFile);
            if (!InFmtCtx.FmtCtxIsOpen) { return; }

            DisplayInfo?.Invoke(InFmtCtx.AudioStream);
            _openAL = new SilkOpenALUtilities();
            _openAL.Init();
            if (!_openAL.Initialization) { return; }

            PlayByFFmpeg();
        }

        /// <summary>
        /// 是否暂停
        /// </summary>
        /// <param name="pause">true:暂停;false:继续;</param>
        public void Pause(bool pause)
        {
            if (IsDisposed || CurCtlStatus == SourceState.Stopped)
                return;

            CurCtlStatus = pause ? SourceState.Paused : SourceState.Playing;
            SourceState curStat = _openAL.GetSourceState();
            if (pause && curStat == SourceState.Playing)
                _openAL.SourcePause();
            else if (!pause && curStat == SourceState.Paused)
                _openAL.SourcePlay();
        }

        public void Stop()
        {
            CurCtlStatus = SourceState.Stopped;
            _openAL.SourceStop();
            Fifo.Clear();
        }

        /// <summary>
        /// 跳转到指定秒
        /// </summary>
        /// <param name="seconds"></param>
        public void Seek(double seconds)
        {
            if (IsDisposed || CurCtlStatus == SourceState.Stopped)
                return;

            Pause(true);
            Fifo?.Clear();
            InFmtCtx.Seek(seconds);
            Pause(false);
        }

        public void AdjustVolume(float vol) => _openAL.SetVolume(vol);

        public void AdjustSpeed(float vol) => _openAL.SetSpeed(vol);

        public void GetAudioInfo(string audioFile, out long bitrate, out int sampleRate, out int channelNum, out int depthBit, out int size)
        {
            throw new NotImplementedException();
        }

        #region dispose
        public bool IsDisposed => disposedValue;
        private bool disposedValue;
        protected void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    disposedValue = true;
                    if (!_cancelSource.IsCancellationRequested)
                        _cancelSource.Cancel();
                    _cancelSource.Dispose();
                    InFmtCtx?.Dispose();
                    Fifo?.Dispose();
                    SwrCtx?.Dispose();
                    _openAL?.Dispose();
                }
                disposedValue = true;
            }
        }

        public void Dispose()
        {
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }
        #endregion

        #region FFmpeg.AutoGen
        private FAVFormatContext InFmtCtx;

        public void PlayByFFmpeg()
        {
            AudioStreamInfo = InFmtCtx.AudioStream;

            CurCtlStatus = SourceState.Playing;
            _cancelSource = new CancellationTokenSource();
            Task.Factory.StartNew(LoadAudioWorkThread, _cancelSource.Token);
            Task.Factory.StartNew(PlayAudioWorkThread, _cancelSource.Token);
            _openAL.SourcePlay();
        }

        public void LoadAudioWorkThread()
        {
            using FAVPacket packet = new FAVPacket();
            using FAVFrame frame = new FAVFrame();
            while (!disposedValue)
            {
                if (NeedLoad())
                    LoadByFFmpeg(packet, frame);
                else
                    ffmpeg.av_usleep(10000u);

                packet.UnRef();
                frame.UnRef();
            }
            packet.Dispose();
            frame.Dispose();
        }

        /// <summary>
        /// 判断是否需要继续加载
        /// </summary>
        /// <returns></returns>
        private bool NeedLoad()
        {
            if (CurCtlStatus != SourceState.Playing)
                return false;
            if (Fifo == null)
                return true;
            int fifoToRead = Fifo.Size();
            return fifoToRead < 20000;
        }

        /// <summary>
        /// OpenAL播放状态控制任务
        /// </summary>
        public void PlayAudioWorkThread()
        {
            while (!disposedValue)
            {
                ffmpeg.av_usleep(10000);
                if (Fifo != null)
                {
                    // 读取排队片段数
                    int queuedNum = _openAL.BuffersQueued();
                    // 读取当前源播放状态
                    SourceState curStat = _openAL.GetSourceState();

                    #region 控制播放状态
                    if (CurCtlStatus == SourceState.Playing && queuedNum > 0 && curStat != SourceState.Playing)
                    {
                        _openAL.SourcePlay();
                    }
                    else if (CurCtlStatus == SourceState.Paused && curStat == SourceState.Playing)
                    {
                        _openAL.SourcePause();
                    }
                    else if (CurCtlStatus == SourceState.Stopped)
                    {
                        _openAL.SourceStop();
                        Dispose();
                        break;
                    }
                    #endregion

                    #region 检查为播放片段的排队数
                    if (CurCtlStatus == SourceState.Playing && queuedNum < 3)
                    {
                        void* pcmData = Fifo.Dequeue(5000, out int dataSize);
                        if (dataSize > 0)
                        {
                            _openAL.QueueBuffer(pcmData, dataSize, target_fmt, SampleRate);
                            System.Runtime.InteropServices.Marshal.FreeHGlobal(new IntPtr(pcmData));
                        }
                    }
                    #endregion
                }

                //检查并清除已播放的缓存
                _openAL.ClearUnqueueBuffers();
            }
        }

        /// <summary>
        /// 通过ffmpeg加载音频块
        /// </summary>
        /// <param name="packet"></param>
        /// <param name="frame"></param>
        /// <returns>采样数</returns>
        /// <exception cref="ArgumentException"></exception>
        private void LoadByFFmpeg(FAVPacket packet, FAVFrame frame)
        {
            if (InFmtCtx == null || !InFmtCtx.FmtCtxIsOpen)
                return;

            try
            {
                int ret = InFmtCtx.ReadPacket(packet);
                if (ret < 0 || packet.StreamIndex != InFmtCtx.AudioIndex)
                    return;

                ret = InFmtCtx.Decode(packet, frame);
                if (ret < 0)
                    return;
            }
            catch (Exception ex) { }

            frame.TimeBase = InFmtCtx.AudioStream.TimeBase;

            if (target_fmt == 0)
            {
                SampleRate = frame.SampleRate;
                ChannelNum = frame.Channels;
                (AVSampleFormat avFmt, BufferFormat alFmt) = SilkOpenALUtilities.AVSampleFmtToALFmt(frame.AudioFormat, frame.Channels);
                target_fmt = alFmt;
                out_sample_fmt = avFmt;
                PerSampleSize = ffmpeg.av_get_bytes_per_sample(out_sample_fmt);
                Fifo ??= new FAudioFifo(out_sample_fmt, ChannelNum);

                SwrCtx ??= new FSwrContext(frame, out_sample_fmt);
                if (SwrCtx.Inited == false)
                    return;

            }

            if (Fifo == null)
                throw new ArgumentException("AudioDecodeUtil->_fifo初始化异常");

            PcmFrame pcm = null;
            try
            {
                pcm = SwrCtx.ConvertAudioPcmData(frame, out_sample_fmt);
                if (pcm.NbSamples > 0)
                    Fifo.Enqueue(pcm.Pcm, pcm.NbSamples);
            }
            finally
            {
                pcm?.Dispose();
            }
            return;
        }

        #endregion

    }
}